﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.extrandom;

namespace GenericGA.generationmaker.selection
{
    /// <summary>
    /// A class which selects individuals by sorting the population by fitness and giving each individual a probability of being selected which is proportional to it's index in the list.
    /// </summary>
    /// <typeparam name="P">The phenotype type, or type of the candidate solutions.</typeparam>
    public class RankSelection<P> : Selection<P>
    {

        /// <summary>
        /// A random number generator.
        /// </summary>
        private readonly ExtRandom randGen;

        /// <summary>
        /// The population from which to select.
        /// </summary>
        private List<Individual<P>> population;

        /// <summary>
        /// The total rank of the population to select from.
        /// </summary>
        private int totalRank;

        /// <summary>
        /// Create a rank selecter.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        public RankSelection(ExtRandom randGen)
        {
            this.randGen = randGen;
        }

        /// <summary>
        /// Set the population to select from.
        /// </summary>
        /// <param name="population">The population from which to select.</param>
        /// <param name="fitnessFunction">The fitness function to be used</param>
        public override void InitSelection(List<Individual<P>> population, GA<P>.FitnessFunctionType fitnessFunction)
        {
            this.population = population;
            
            //Sort the population such that index 0 of the population list has the weakest fitness individual.
            this.population.Sort(
                delegate(Individual<P> i1, Individual<P> i2)
                {
                    return i1.CachedFitness.CompareTo(i2.CachedFitness);
                }
            );
            this.totalRank = population.Count * (population.Count + 1) / 2;
        }

        /// <summary>
        /// Select an individual.
        /// </summary>
        /// <returns>An individual from the population.</returns>
        public override Individual<P> Select()
        {
            //Select a random individual biasing the selection towards larger indices of the population list.
            int r = randGen.Next(totalRank);
            int i = (int)(Math.Sqrt(2 * r + 0.25) - 0.5);
            return population[i];
        }

    }
}
